package com.tbit.uqbike.client.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.tbit.common.constant.OperationPlatform;
import com.tbit.common.entity.MachineOpLog;
import com.tbit.uqbike.center.pojo.MachineStatus;
import com.tbit.uqbike.client.business.machine_op_log.service.MachineOpLogService;
import com.tbit.uqbike.client.constant.*;
import com.tbit.uqbike.client.constant.enums.ControlWay;
import com.tbit.uqbike.client.dao.core.MachineFunctionDao;
import com.tbit.uqbike.client.dao.core.ParkDao;
import com.tbit.uqbike.client.dao.log.OrderLogDao;
import com.tbit.uqbike.client.dao.log.ParkLogDao;
import com.tbit.uqbike.client.dao.ter.TerAttrExDao;
import com.tbit.uqbike.client.dao.ter.TerInfoDao;
import com.tbit.uqbike.client.exception.BaseException;
import com.tbit.uqbike.client.pojo.*;
import com.tbit.uqbike.client.service.*;
import com.tbit.uqbike.client.service.prohibitarea.CheckProhibitAreaResult;
import com.tbit.uqbike.client.util.DateTimeUtil;
import com.tbit.uqbike.client.util.DoubleUtil;
import com.tbit.uqbike.client.util.LogUtil;
import com.tbit.uqbike.client.util.ObjectUtil;
import com.tbit.uqbike.client.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.transaction.Transactional;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Service("parkService")
@Slf4j
public class ParkServiceImpl implements ParkService {
    @Autowired
    private ParkDao parkDao;
    @Autowired
    private ParkLogDao parkLogDao;
    @Autowired
    private MachineService machineService;
    @Autowired
    private MachineBorrowService machineBorrowService;
    @Autowired
    private TerControlService terControlService;
    @Autowired
    private AccountService accountService;
    @Autowired
    private RideLogService rideLogService;
    @Autowired
    private MachineLockLogService machineLockLogService;
    @Autowired
    private RedisService redisService;
    @Autowired
    private MachineStatusService machineStatusService;
    @Autowired
    private GeoService geoService;
    @Autowired
    private AdAccountFeeService adAccountFeeService;
    @Autowired
    private OrderLogDao orderLogDao;
    @Autowired
    private MachineFunctionDao machineFunctionDao;
    @Autowired
    private TerInfoDao terInfoDao;
    @Autowired
    private ProhibitAreaService prohibitAreaService;
    @Autowired
    private TerAttrExDao terAttrExDao;
    @Autowired
    private CacheService cacheService;
    @Autowired
    private ForbiddenService forbiddenService;
    @Autowired
    private MachineOpLogService machineOpLogService;
    @Autowired
    private UserServiceImpl userService;

    @Override
    public Park getByUserId(Integer userId) {
        Park park = parkDao.getByUserId(userId);
        if (park != null) {
            Machine machine = machineService.getByMachineId(park.getMachineId());
            if (machine != null) {
                AccountConfig accountConfig = cacheService.getConfig(machine.getAccountId());
                if (accountConfig.getFinishOrderSwitch()) {
                    Integer finishTime = accountConfig.getFinishTime();
                    Map<String, Object> attrMap = new HashMap<>();
                    attrMap.put("machineNO", machine.getMachineNO());
                    attrMap.put("name", "tLST");
                    TerAttrEx terAttrEx = terAttrExDao.getByMachineNO(attrMap);
                    if (terAttrEx != null && Integer.valueOf(terAttrEx.getValue()) == 0) {
                        //临停开始时间跟设备静止时间取最小值
                        String time = park.getStartTime();
                        if (DateTimeUtil.StringToDateTime(time).getTime() > terAttrEx.getDt().getTime()) {
                            time = DateTimeUtil.DateTimeToString(terAttrEx.getDt());
                        }
                        String finishDate = DateTimeUtil
                                .addMinutes(time, finishTime);
                        if (DateTimeUtil.getTimeDF(DateTimeUtil.getNowTime(), finishDate) < 0) {
                            park.setParkTime(0);
                        } else {
                            park.setParkTime(DateTimeUtil.getTimeDF(DateTimeUtil.getNowTime(), finishDate));
                        }
                    } else {
                        park.setParkTime(DateTimeUtil.getTimeDF(DateTimeUtil.getNowTime(), DateTimeUtil
                                .addMinutes(park.getStartTime(), finishTime)));
                    }
                } else {
                    park.setParkTime(-1);
                }
            }

        }
        return park;
    }

    @Override
    public Park getByMachineId(Integer machineId) {
        return parkDao.getByMachineId(machineId);
    }

    @Override
    public void insert(Park park) {
        parkDao.insert(park);

    }

    @Override
    public void delByUserId(Integer userId) {
        parkDao.delByUserId(userId);
    }

    @Override
    public void addLog(Park park) {
        parkLogDao.insert(park);
    }

    @Override
    @Transactional
    public void parkRide(String serNo, Integer userId, Boolean ble, Integer orderSource,
                         Double phonePositionLon, Double phonePositionLat) {
        //查询用户是否存在骑行记录
        MachineBorrow machineBorrow = machineBorrowService.getByUserId(userId);
        if (machineBorrow == null) {
            throw new BaseException("-3007", "不存在骑行记录");
        }

        //查询设备信息
        Machine machine = machineService.getByMachineId(machineBorrow.getMachineId());
        //是否存在临时停车记录
        Park park = getByMachineId(machineBorrow.getMachineId());
        if (park != null) {
            if(ble){
                delByUserId(userId);
            }else{
                throw new BaseException("-3030", "该车已经存在临时停车记录");
            }
        }

        //兼容低电断电不许临停
        String tmp = redisService.get(RedisConstant.MACHINE_BORROW_MAIN + machine.getMachineNO());
        if (tmp != null) {
            throw new BaseException("-3031", "电量过低，请更换一辆车");
        }
        Map<String, Object> param = new HashMap<>();
        param.put("machineId", machine.getMachineId());
        param.put("functionType", MachineStateConstant.MACHINE_TYPE_HELMAT);
        //兼容头盔锁功能
        MachineFunction machineFunction = machineFunctionDao.getByMachineId(param);
        //开关打开
        if (machineFunction != null && machineFunction.getFunctionValue() == 1) {
            String json = redisService.getMain(
                    RedisUtil.getTerHashId(machine.getMachineNO()), RedisConstant.REDIS_TER_LAST_STATUS);
            if (json != null && json.length() > 0) {
                String startStatus = redisService.getWay(RedisConstant.MACHINE_NO_HELMET + userId);
                boolean state = false;
                if (startStatus != null) {
                    state = ObjectUtil.statusParse(11, Integer.valueOf(startStatus));
                }
                TerLastStatus terLastStatus = JSON.parseObject(json, TerLastStatus.class);
                Integer terStatus = terLastStatus.getCarStatus();
                LogUtil.info("临时停车头盔锁状态=" + terStatus + "，" + ObjectUtil.statusParse(11, terStatus));
                if (ObjectUtil.statusParse(11, terStatus) && !state) {
                    throw new BaseException("-140001", "头盔锁未放置成功");
                }
            }
        }
        Map<String, Object> map = new HashMap<>();
        map.put("machineId", machine.getMachineId());
        map.put("functionType", MachineStateConstant.MACHINE_TYPE_NOPOINT);
        //兼容头盔锁功能
        MachineFunction machineFunctionDB = machineFunctionDao.getByMachineId(map);

        //判断用户是否在运营区域，不在运营区域不能临时停车
        MachineStatus machineStatus = machineStatusService.getByMachineNO(machine.getMachineNO(), MapConstant.MAP_BAIDU);
        if (machineStatus != null && machineStatus.getLon() != null && machineStatus.getLat() != null
                && Boolean.FALSE.equals(ble)&&(machineFunctionDB== null || machineFunctionDB.getFunctionValue() == 0)) {
            Point point = new Point(machineStatus.getLon(), machineStatus.getLat());
            Boolean isInGeo = geoService.checkInGeo(machine.getAccountId(), point);
            if (!isInGeo) {
                throw new BaseException("-3046", "运营区域外禁止临时停车");
            }
            //临时停车禁停区不许停车
            CheckProhibitAreaResult checkInProhibitArea = prohibitAreaService.checkInProhibitArea(machine.getAccountId(), point);
            Integer prohibitAreaId = checkInProhibitArea.inProhibitArea() ? checkInProhibitArea.getProhibitArea()
                                                                                               .getProhibitAreaId() : null;
            if (prohibitAreaId != null) {
                throw new BaseException("-60003", "车辆在禁停区内，禁停区内禁止临时停车");
            }

            //禁行区校验
            Boolean result = forbiddenService.checkInForbidden(machine.getUserCode(), point);
            if (result) {
                throw new BaseException("-4007", "车辆在禁行区内,请更换车辆");
            }
        }


        /**添加业务信息*/
        BorrowReturnTer info = new BorrowReturnTer();
        info.setType(1);
        info.setAccountId(machine.getAccountId());
        info.setMachineId(machine.getMachineId());
        info.setUserId(userId);
        info.setMachineNO(machine.getMachineNO());
        info.setOrderNO(machineBorrow.getOrderNO());

        redisService.add(serNo, JSONObject.toJSONString(info), 60);
        OrderLog orderLog = new OrderLog();
        /**判断是否是GPRS通道*/
        if (!ble) {
            /**machineSofewareInfoMap中包含的软件版本号和终端上线时间大于20年8月1号认为支持新的临时停车语音*/
            TerInfo terInfo = terInfoDao.getByMachineNO(machine.getMachineNO());
            if (terInfo != null && (
                    DateTimeUtil.getTimeDF("2020-08-01 00:00:00", terInfo.getJoinTime()) > 0 || SystemData.machineSofewareInfoMap.containsKey(terInfo.getSoftVersion()))) {
                /**远程控制-临时停车*/
                terControlService.sendControl(serNo, machine.getMachineNO(), ControlTypeConstant.CONTROL_TYPE_CONTROL, ControlTypeConstant.CONTROL_TYPE_PARKRIDE, "", machine, false, orderSource, userId, "用户中途停车上锁[GPRS]");
                orderLog.setOrderContent(ControlTypeConstant.CONTROL_TYPE_PARKRIDE);
            } else {
                /**控制车辆上锁*/
                terControlService.sendControl(serNo, machine.getMachineNO(), ControlTypeConstant.CONTROL_TYPE_CONTROL, ControlTypeConstant.CONTROL_TYPE_LOCK, "", machine, false, orderSource, userId, "用户中途停车上锁[GPRS]");
                orderLog.setOrderContent(ControlTypeConstant.CONTROL_TYPE_LOCK);
            }
        } else {
            TerAttrEx terAttrEx = new TerAttrEx();
            terAttrEx.setDt(new Date());
            terAttrEx.setMachineNO(machine.getMachineNO());
            terAttrEx.setName("tLST");
            terAttrExDao.edit(terAttrEx);
            /**记录日志信息 */
            orderLog.setOrderSerNo(serNo);
            orderLog.setMachineId(machine.getMachineId());
            orderLog.setMachineNo(machine.getMachineNO());
            orderLog.setUserCode(machine.getUserCode());
            orderLog.setChannelType(Integer.valueOf(ble ? ControlWay.CLIENT_BLE.getCode() : ControlWay.CLIENT_GPRS.getCode()));
            orderLog.setBusinessType(OrderLogTypeConstant.BUSINESSTYPE_BORROW);
            orderLog.setOpTime(DateTimeUtil.getNowTime());
            orderLog.setOpType(OrderLogTypeConstant.OPTYPE_USER);
            //指令来源
            orderLog.setOrderSource(orderSource);
            orderLog.setOrderType(ControlTypeConstant.CONTROL_TYPE_CONTROL);
            orderLog.setOpId(userId);
            orderLog.setRemark("用户中途停车上锁[BLE]");
            orderLog.setAccountUserId(machine.getAccountId());
            orderLog.setRspDt(DateTimeUtil.getNowTime());
            orderLog.setRspRet(1);
            orderLog.setRspContent("1");
            orderLogDao.addBle(orderLog);
        }

        // 记录车辆操作日志
        User user = userService.getByUserId(userId);
        String detail = "骑行单号: " + machineBorrow.getOrderNO() + "\n指令流水号: " + serNo;
        machineOpLogService.insertSingle(MachineOpLog.builder()
                .accountId(machine.getAccountId())
                .machineId(machine.getMachineId())
                .machineNO(machine.getMachineNO())
                .userCode(machine.getUserCode())
                .operation("临时停车")
                .opType("临时停车")
                .opUser(user.getName())
                .opUserPhone(user.getPhone())
                .opPlatform(OperationPlatform.CLIENT.code)
                .detail(detail)
                .result(true)
                .opTime(LocalDateTime.now())
                .build()
        );
    }

    @Resource
    private CalibrateService calibrateService;

    /**
     * 写这个完全是为了给前端提供 蓝牙通道锁车的前置校验
     *
     * @param userId           用户id
     * @param type             判断类型  0=临时停车  1=继续骑行
     * @param phonePositionLon 手机经度
     * @param phonePositionLat 手机纬度
     * @return 。
     */
    @Override
    public Boolean machinePositionCheck(Integer userId, Integer type, Double phonePositionLon, Double phonePositionLat) {
        //查询用户是否存在骑行记录
        MachineBorrow machineBorrow = machineBorrowService.getByUserId(userId);
        if (machineBorrow == null) {
            throw new BaseException("-3007", "不存在骑行记录");
        }
        //查询设备信息
        Machine machine = machineService.getByMachineId(machineBorrow.getMachineId());
        Point point = new Point(phonePositionLon, phonePositionLat);
        String tmp = redisService.get(RedisConstant.MACHINE_BORROW_MAIN + machine.getMachineNO());
        if (tmp != null) {
            throw new BaseException("-3031", "电量过低，请更换一辆车");
        }
        point = calibrateService.calibrate(point, SysConstant.GOOGLE, SysConstant.ORIGINAL);
        Boolean isInGeo = geoService.checkInGeo(machine.getAccountId(), point);
        log.info("转换后经纬度:{}  对比区域结果 {} ", JSON.toJSONString(point), isInGeo);
        //判断类型  0=临时停车  1=继续骑行
        if (Objects.equals(0, type)) {
            //是否存在临时停车记录
            if (Boolean.FALSE.equals(isInGeo)) {
                throw new BaseException("-3046", "运营区域外禁止临时停车");
            }
            //临时停车禁停区不许停车
            CheckProhibitAreaResult checkInProhibitArea = prohibitAreaService.checkInProhibitArea(machine.getAccountId(), point);
            Integer prohibitAreaId = checkInProhibitArea.inProhibitArea() ? checkInProhibitArea.getProhibitArea()
                                                                                               .getProhibitAreaId() : null;
            if (prohibitAreaId != null) {
                throw new BaseException("-60003", "车辆在禁停区内，请在禁停区外还车");
            }
            //禁行区校验
            boolean result = forbiddenService.checkInForbidden(machine.getUserCode(), point);
            if (result) {
                throw new BaseException("-4007", "车辆在禁行区内,请更换车辆");
            }
        } else if (Objects.equals(1, type)) {
            //是否有中途停车记录
            Park park = getByUserId(userId);
            if (park == null) {
                throw new BaseException("-3011", "不存在中途停车记录");
            }
            if (Boolean.FALSE.equals(isInGeo)) {
                throw new BaseException("-3050", "运营区域外,请在运营区域内使用");
            }
            //禁行区校验
            boolean result = forbiddenService.checkInForbidden(machine.getUserCode(), point);
            if (result) {
                throw new BaseException("-4007", "车辆在禁行区内,请更换车辆");
            }
        }
        return true;
    }

    @Override
    @Transactional
    public void continueRide(String serNO, Integer userId, Boolean ble, Integer orderSource) {
        //查询用户是否存在骑行记录
        MachineBorrow machineBorrow = machineBorrowService.getByUserId(userId);
        if (machineBorrow == null) {
            throw new BaseException("-3007", "不存在骑行记录");
        }
        //是否有中途停车记录
        Park park = getByUserId(userId);
        if (park == null &&!ble) {
            throw new BaseException("-3011", "不存在中途停车记录");
        }
        //查询设备信息
        Machine machine = machineService.getByMachineId(machineBorrow.getMachineId());
        //判断用户是否在运营区域，不在运营区域不能继续骑行
        MachineStatus machineStatus = machineStatusService.getByMachineNO(machine.getMachineNO(), MapConstant.MAP_BAIDU);

        Map<String, Object> map = new HashMap<>();
        map.put("machineId", machine.getMachineId());
        map.put("functionType", MachineStateConstant.MACHINE_TYPE_NOPOINT);
        //兼容头盔锁功能
        MachineFunction machineFunctionDB = machineFunctionDao.getByMachineId(map);
        //蓝牙不走这段校验 自行车才走
        if (machineStatus != null && machineStatus.getLon() != null && machineStatus.getLat() != null
                && Boolean.FALSE.equals(ble)&&(machineFunctionDB== null || machineFunctionDB.getFunctionValue() == 0)) {
            Point point = new Point(machineStatus.getLon(), machineStatus.getLat());
            Boolean isInGeo = geoService.checkInGeo(machine.getAccountId(), point);
            log.info("continueRide 转换后经纬度:{}  对比区域结果 {} ", JSON.toJSONString(point), isInGeo);
            if (!isInGeo) {
                throw new BaseException("-3050", "运营区域外,请在运营区域内使用");
            }
            /**禁行区校验*/
            Boolean result = forbiddenService.checkInForbidden(machine.getUserCode(), point);
            if (result) {
                throw new BaseException("-4007", "车辆在禁行区内,请更换车辆");
            }
        }
        /**兼容低电断电不许临停*/
        String tmp = redisService.get(RedisConstant.MACHINE_BORROW_MAIN + machine.getMachineNO());
        if (tmp != null) {
            throw new BaseException("-3031", "电量过低，请更换一辆车");
        }
        /**添加业务信息*/
        BorrowReturnTer info = new BorrowReturnTer();
        info.setType(1);
        info.setAccountId(machine.getAccountId());
        info.setMachineId(machine.getMachineId());
        info.setUserId(userId);
        info.setMachineNO(machine.getMachineNO());
        info.setOrderNO(machineBorrow.getOrderNO());

        redisService.add(serNO, JSONObject.toJSONString(info), 60);

        /**判断是否是GPRS通道*/
        if (!ble) {
            /**machineSofewareInfoMap中包含的软件版本号和终端上线时间大于20年8月1号认为支持新的临时停车语音*/
            TerInfo terInfo = terInfoDao.getByMachineNO(machine.getMachineNO());
            if (terInfo != null && (
                    DateTimeUtil.getTimeDF("2020-08-01 00:00:00", terInfo.getJoinTime()) > 0 || SystemData.machineSofewareInfoMap.containsKey(terInfo.getSoftVersion()))) {
                /**远程控制-继续骑行*/
                terControlService.sendControl(serNO, machine.getMachineNO(), ControlTypeConstant.CONTROL_TYPE_CONTROL, ControlTypeConstant.CONTROL_TYPE_CONTINUERIDE, "", machine, false, orderSource, userId, "用户中途停车开锁[GPRS]");
            } else {
                /**控制车辆开锁*/
                terControlService
                        .sendControl(serNO, machine.getMachineNO(), ControlTypeConstant.CONTROL_TYPE_CONTROL,
                                ControlTypeConstant.CONTROL_TYPE_UNLOCK, "", machine, false, orderSource, userId, "用户中途停车开锁[GPRS]");
            }
        } else {
            TerAttrEx terAttrEx = new TerAttrEx();
            terAttrEx.setDt(new Date());
            terAttrEx.setMachineNO(machine.getMachineNO());
            terAttrEx.setName("tLST");
            terAttrExDao.edit(terAttrEx);
            /**记录日志信息 */
            OrderLog orderLog = new OrderLog();
            orderLog.setOrderSerNo(serNO);
            orderLog.setMachineId(machine.getMachineId());
            orderLog.setMachineNo(machine.getMachineNO());
            orderLog.setUserCode(machine.getUserCode());
            orderLog.setChannelType(Integer.valueOf(ble ? ControlWay.CLIENT_BLE.getCode() : ControlWay.CLIENT_GPRS.getCode()));
            orderLog.setBusinessType(OrderLogTypeConstant.BUSINESSTYPE_BORROW);
            orderLog.setOpTime(DateTimeUtil.getNowTime());
            orderLog.setOpType(OrderLogTypeConstant.OPTYPE_USER);
            orderLog.setOrderContent(ControlTypeConstant.CONTROL_TYPE_CONTINUERIDE);
            /**指令来源*/
            orderLog.setOrderSource(orderSource);
            orderLog.setOrderType(ControlTypeConstant.CONTROL_TYPE_CONTROL);
            orderLog.setOpId(userId);
            orderLog.setRemark("用户中途停车开锁" + (ble ? "[BLE]" : "[GPRS]"));
            orderLog.setAccountUserId(machine.getAccountId());
            orderLog.setRspDt(DateTimeUtil.getNowTime());
            orderLog.setRspRet(1);
            orderLog.setRspContent("1");
            orderLogDao.addBle(orderLog);
        }

        /**设备最后开锁时间*/
        redisService.add("MACHINELAST" + machine.getMachineNO(), DateTimeUtil.getNowTime(), 24 * 3600);

        // 记录车辆操作日志
        User user = userService.getByUserId(userId);
        String detail = "骑行单号: " + machineBorrow.getOrderNO() + "\n指令流水号: " + serNO;
        machineOpLogService.insertSingle(MachineOpLog.builder()
                .accountId(machine.getAccountId())
                .machineId(machine.getMachineId())
                .machineNO(machine.getMachineNO())
                .userCode(machine.getUserCode())
                .operation("临时停车")
                .opType("继续骑行")
                .opUser(user.getName())
                .opUserPhone(user.getPhone())
                .opPlatform(OperationPlatform.CLIENT.code)
                .detail(detail)
                .result(true)
                .opTime(LocalDateTime.now())
                .build()
        );
    }

    @Override
    public Double parkFee(AdAccountFee accountFee, Integer timeMinute) {
        Double money = 0D;
        if (accountFee.getStopFee() != null) {
            /**计算临时停车费用*/
            /**起步费用*/
            money = (double) accountFee.getStopFee();
            /**超出部分费用*/
            money = money + accountFee.getStopFee() * timeMinute;
        }

        return DoubleUtil.format(money);
    }

    @Override
    public Park getParking(Integer userId) {
        Park park = getByUserId(userId);
        if (park != null) {
            /**查询用户骑行订单*/
            RideLog rideLog = rideLogService.getByUserIdNotFinish(userId);
            if(rideLog==null){
                return null;
            }
            /**计算预估费用*/
            /**查询品牌是否定义费用，如果没有按照代理商定义费用收取*/
            Account account = accountService.getByAccountId(rideLog.getAccountId());

            AdAccountFee adAccountFee = adAccountFeeService.getByAccountId(account.getAccountId(), park.getMachineId());
            /**骑行订单*/
            if (rideLog != null) {
                park.setMoney(DoubleUtil.format(getParkFee(park, rideLog, adAccountFee)));
            }
        }
        return park;
    }

    public Point determinePoint(Machine machine, MachineStatus machineStatus,
                                Double phonePositionLon, Double phonePositionLat) {
        //自行车是用手机定位(前提是前端传递了手机定位)
        if (machine.machineTypeIsBicycle() &&
                cn.hutool.core.util.ObjectUtil.isAllNotEmpty(phonePositionLon, phonePositionLat)) {
            return new Point(phonePositionLon, phonePositionLat);
        }
        return new Point(machineStatus.getLon(), machineStatus.getLat());
    }


    @Override
    public Double getParkFee(Park park, RideLog rideLog, AdAccountFee accountFee) {
        /**设置预估费用*/
        Double money = 0D;

        Integer minutes = rideLog.getStopTime() / 60;
        if (rideLog.getStopTime() % 60 > 0) {
            minutes++;
        }
        if (park != null) {
            minutes = minutes + DateTimeUtil.getMinDiffer(park.getStartTime());
        }
        if (minutes > 0) {
            money = money + parkFee(accountFee, minutes);
        }

        return money;
    }

    @Override
    public void delByMachineId(Integer machineId) {
        parkDao.delByMachineId(machineId);
    }

}